package pl.edu.utp.ga.genotypes;

import java.util.ArrayList;
import java.util.List;

import pl.edu.utp.ga.genes.BinaryGene;
import pl.edu.utp.ga.interfaces.Calculation;
import pl.edu.utp.ga.interfaces.Gene;
import pl.edu.utp.ga.interfaces.Genotype;

public class BinaryGenotype extends BasicGenotype{
	
	public BinaryGenotype(){
		this(Double.SIZE*2);
	}
	
	public BinaryGenotype(int size){
		super(size);
	}
	
	public BinaryGenotype(ArrayList<Gene<?>> genes){
		super(genes);
	}
	
	private double generateValue(int index){
		double value;
		if(genesRanges == null){
			value = random.nextDouble()*Double.MAX_VALUE-(Double.MAX_VALUE/2);
		} else{
			value = random.nextDouble()*(genesRanges.get(index).getTo()-genesRanges.get(index).getFrom())+genesRanges.get(index).getFrom();
		}
		return value;
	}
	
	public String toBinaryString(double number){
		String value = Long.toBinaryString(Double.doubleToLongBits(number));
		int numOfZeros = Double.SIZE - value.length();
		for(int i = 0; i < numOfZeros; i++){
			value = String.format("0%s", value);
		}
		return value;
	}
	
	public double toDouble(String binaryString){
		double value = Double.MAX_VALUE;
		boolean isNegative = false;
		try{
			if(binaryString.charAt(0) == '1'){
				binaryString = "0"+binaryString.substring(1);
				isNegative = true;
			}
			 double actualValue = Double.longBitsToDouble(Long.valueOf(binaryString,2));
			 if(actualValue < value){
				 if(isNegative)
					 actualValue = -actualValue;
				 return actualValue;
			 }
		}catch(NumberFormatException ex){}
		return value;
	}
	
	@Override
	public void generate() {
		genes.clear();
		for(int i=0; i<(size/Double.SIZE); i++){
			double value = generateValue(i);
			String binary = toBinaryString(value);
			for(int j=0;j<binary.length();j++){
				boolean bit = (binary.charAt(j) == '1');
				BinaryGene gene = new BinaryGene(bit);
				if(genes.size() < size){
					genes.add(gene);
				} else{
					genes.set(i, gene);
				}
			}
		}
	}
	
	@SuppressWarnings("rawtypes")
	@Override
	public ArrayList<Genotype> cross(List<? extends Gene> secondParentGenes) {
		
		ArrayList<Genotype> genotypes = new ArrayList<Genotype>();
		int splitPoint = getSplitPoint();
		
		ArrayList<Gene<?>> firstChildGenes = new ArrayList<>();
		ArrayList<Gene<?>> secondChildGenes = new ArrayList<>();
		
		for(int i=0;i<size;i++){
			if(i < splitPoint){
				BinaryGene g1 = new BinaryGene(((Boolean)genes.get(i).get()).booleanValue());
				BinaryGene g2 = new BinaryGene(((Boolean)secondParentGenes.get(i).get()).booleanValue());
				
				firstChildGenes.add(g1);
				secondChildGenes.add(g2);
			}else{
				BinaryGene g1 = new BinaryGene(((Boolean)genes.get(i).get()).booleanValue());
				BinaryGene g2 = new BinaryGene(((Boolean)secondParentGenes.get(i).get()).booleanValue());
				
				secondChildGenes.add(g1);
				firstChildGenes.add(g2);
			}
		}	
		genotypes.add(new BinaryGenotype(firstChildGenes));
		genotypes.add(new BinaryGenotype(secondChildGenes));
		return genotypes;
	}

	@Override
	public void mutate() {
		int genePosition = genePosition();
		boolean mutation = getMutation();
		BinaryGene gene = (BinaryGene)genes.get(genePosition);
		gene.mutate(mutation);
		
	}
	
	private boolean getMutation(){
		return random.nextBoolean();
	}

	@Override
	public double rate(Calculation calculation) {
		
		double[] geneValues = new double[size/Double.SIZE];
		for(int i=0;i<size/Double.SIZE;i++){
			double value = getDoubleRepresentation(i);
			geneValues[i] = value;
		}
		return calculation.calculate(geneValues);	
	}
	
	public double getDoubleRepresentation(int position){
		StringBuilder value = new StringBuilder();
		for(int i=(Double.SIZE*position);i<Double.SIZE + (Double.SIZE*position);i++){
			value.append(genes.get(i));
		}
		return toDouble(value.toString());
	}
	
	public String toString(){
		StringBuilder genotype = new StringBuilder();
		for(int i=0;i<genes.size();i++){
			genotype.append(genes.get(i));
		}
		return genotype.toString();
	}
	
	public String toDoubleString(){
		String representation = "";
		for(int i=0;i<size/Double.SIZE;i++){
			double value = getDoubleRepresentation(i);
			representation = String.format("%s %.6f", representation,value);
		}
		return representation;
	}

}
